#ifndef _FNT_LOAD_THREAD_H_
#define _FNT_LOAD_THREAD_H_

#include <QThread>
#include <QColor>
#include <array>

#include "image.h"

class Session;
struct LoadThreadShared;
struct CubeData;
struct LoadThreadPriv;

class Location;
class AnnotTree;
struct CubeId {
	const Location* location; //*
	const AnnotTree* annotTree; //*
	int32_t x0, y0, z0; //*
	int nx, ny, nz; //*

	bool operator==(const CubeId& r) const {
		if(r.location!=location) return false;
		if(r.annotTree!=annotTree) return false;
		if(r.x0!=x0) return false;
		if(r.y0!=y0) return false;
		if(r.z0!=z0) return false;
		if(r.nx!=nx) return false;
		if(r.ny!=ny) return false;
		if(r.nz!=nz) return false;
		return true;
	}
	int size() const {
		return nx*ny*nz;
	}
};

class CubeDataRef {
	friend struct LoadThreadShared;
	friend struct LoadThreadPriv;

	CubeData* _cube;

	CubeDataRef(CubeData* f); // called when locked

	CubeData* operator->() const { return _cube; }

	public:

	CubeDataRef(): _cube{nullptr} { }
	~CubeDataRef() {
		release();
	}

	CubeDataRef(const CubeDataRef& r);
	CubeDataRef& operator=(const CubeDataRef& r);

	CubeDataRef(CubeDataRef&& r):
		_cube{r._cube}
	{
		r._cube=nullptr;
	}
	CubeDataRef& operator=(CubeDataRef&& r) {
		std::swap(_cube, r._cube);
		return *this;
	}

	void release();

	bool operator!() const { return !_cube; }
	operator bool() const { return _cube; }

	CubeType type() const;
	int64_t width() const;
	int64_t height() const;
	int64_t depth() const;
	int64_t widthAdj() const;
	int64_t heightAdj() const;
	int64_t depthAdj() const;
	int64_t size(int axis) const;
	const char* data() const;
	const CubeId& cubeId() const;
};

struct CubeXform {
	std::array<double, 3> origin; //*
	std::array<double, 9> direction; //*
	std::array<double, 9> direction_inv;
	std::array<double, 3> resolution;
	bool operator==(const CubeXform& r) const {
		for(int i=0; i<3; i++)
			if(origin[i]!=r.origin[i])
				return false;
		for(int i=0; i<9; i++)
			if(direction[i]!=r.direction[i])
				return false;
		return true;
	}
	CubeXform():
		origin{0.0, 0.0, 0.0},
		direction{1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0} { }
	CubeXform(const std::array<double, 3>& o, const std::array<double, 9>& d):
		origin(o),
		direction(d) { }
};

struct Cube {
	CubeDataRef data;
	CubeXform xform;
};

struct LoadThreadInput {
	QString location;
	std::array<double, 3> origin;
	std::array<double, 9> direction;
};

struct CacheThread;

class LoadThread: public QThread {
	Q_OBJECT;
	friend struct LoadThreadPriv;
	LoadThreadPriv*const priv;

	void run() override;

Q_SIGNALS:
	void threadWarning(const QString& msg);
	void threadError(const QString& msg);

	void sourcesReady();

	void cubeFinished();
	void updateProgress(int cur, int tot);

	public:
	LoadThread(Session* s);
	LoadThread(Session* s, const LoadThreadInput& input);
	~LoadThread();

	QVector<QString> sources() const;
	bool getBoundingBox(std::array<double, 6>* bbox) const;
	bool getMinVoxelSize(double* vsize) const;
	std::vector<std::pair<int, Cube>> getOutput();
	bool reallyFinished() const;

	void stop();

	bool setPosition(int ch, int idx, double x, double y, double z, double sight, const Cube& cur);
	void cancel();

	static LoadThreadShared* createShared();
	static void destroyShared(LoadThreadShared* lts);
};

#endif
